#include "FKClient.h"
#include "FKEvent.h"
#include "FKUser.h"
#include "FKObjectsData.h"
#include "FKRoom.h"
#include "FKEventNames.h"
#include <QSettings>

#include <QStringList>
#include <QTimer>

#include "FKLogger.h"
#include "FKPaths.h"

FK_EVENTS(FKClient){
    events.insert(FKEventName::g_error,&FKClient::AE_gotSystemMessage);
    events.insert(FKEventName::e_addUser,&FKClient::E_addUser);
    events.insert(FKEventName::e_removeUser,&FKClient::E_removeUser);
    events.insert(FKEventName::g_createObjects,&FKClient::E_createObjects);
    events.insert(FKEventName::g_deleteObjects,&FKClient::E_deleteObjects);
    events.insert(FKEventName::e_updateRoomList,&FKClient::E_updateRoomList);
    events.insert(FKEventName::e_submitUsers,&FKClient::E_submitUsers);
    events.insert(FKEventName::e_initRoomModule,&FKClient::e_initRoomModule);

    actions.insert(FKEventName::a_setPassword,&FKClient::A_setPassword);
    actions.insert(FKEventName::a_setName,&FKClient::A_setName);
    actions.insert(FKEventName::a_addUserToList,&FKClient::A_addUserToList);
    actions.insert(FKEventName::a_removeUserFromList,&FKClient::A_removeUserFromList);
    actions.insert(FKEventName::g_error,&FKClient::AE_gotSystemMessage);
    actions.insert(FKEventName::a_addUser,&FKClient::A_addUser);
    actions.insert(FKEventName::a_removeUser,&FKClient::A_removeUser);
    actions.insert(FKEventName::a_updateRoomList,&FKClient::A_updateRoomList);
    actions.insert(FKEventName::a_avaliableRoomTypes,&FKClient::a_avaliableRoomTypes);

    props<<FKEventName::c_password;
    props<<FKEventName::c_name;
    props<<FKEventName::c_userList;
}

QMap<QString,FKClient*> FKClient::_clients;
const int FKClient::_deletingDelay=600000;

int FKClient::regMetaTypes(){
    qRegisterMetaTypeStreamOperators<FKRoomDataList>("FKRoomDataList");
    return qRegisterMetaType<FKRoomDataList>("FKRoomDataList");
}

int FKClient::identifier=FKClient::regMetaTypes();

FKClient::FKClient(FKRoom *room, qint32 objectID):FKObject(room,objectID),_name(),_password(),_connected(false),_reconnecting(false){
    FK_CBEGIN
//    allowEmitBy(this);
//    setCustomWatching(this);
    _deleteTimer=0;
    FK_CEND
}

FKClient::FKClient(QString clientName, QString password):FKObject(),_name(), _password(),_connected(false),_reconnecting(false){
    FK_CBEGIN
    allowEmitBy(this);
    setCustomWatching(this);
    setName(clientName);
    setPassword(password);
    if(clientName!=QString()){
        QSettings settings;
        _userList=settings.value(FKPaths::users+clientName).toStringList();
    }
    _deleteTimer=new QTimer(this);
    _deleteTimer->setInterval(_deletingDelay);
    connect(_deleteTimer,SIGNAL(timeout()),SLOT(releaseClient()));
    FK_CEND
}

FKClient::~FKClient(){
    FK_DBEGIN
    if(_name!=QString())_clients.remove(_name);
    FK_DEND
}

FKClient* FKClient::getClient(QString clientName){
    return _clients.value(clientName,0);
}

QSet<FKObject*> FKClient::sharedVisors(){
    QSet<FKObject*> s;
    s.insert(this);
    return s;
}

void FKClient::visionChanged(){
    if(!_connected)return;
    QSet<FKObject*> w(totalWatched());
    QSet<FKObject*> forDeleting(_hasObjects);
    forDeleting.subtract(w);
    QSet<FKObject*> forCreation(w);
    forCreation.subtract(_hasObjects);
    if(!forDeleting.isEmpty())emit newEvent(FKEvent::deleteObjectsEvent(getID(),forDeleting));
    if(!forCreation.isEmpty()){
        emit newEvent(FKEvent::createObjectsEvent(getID(),forCreation));
        foreach(FKObject* obj,forCreation)obj->refreshValues(this);
    }
    _hasObjects=w;
}

void FKClient::manageEvent(FKEvent* event){
//    FK_MLOGV("client recieved event",event->subject())
    if(event->object()==getID() || event->subject()==FKEventName::g_error){
        processFKEvent(event);
    }else{
        FKUser* user=_users.value(event->owner(),0);
        if(user){
//            FK_MLOGV("client pass event to user",event->subject())
            user->manageEvent(event);
        }else{
            FKObject* obj=getObject(event->object());
            if(obj){
//                FK_MLOGV("client pass event to object",event->subject())
                obj->processFKEvent(event);
            }else{
                FKEvent* errorEvent=FKEvent::errorEvent(event);
                event->deleteLater();
                emit newEvent(errorEvent);
            }
        }
    }
}

void FKClient::manageAction(FKEvent* action){
    if(action->object()==getID()){
        processFKAction(action);
    }else{
        FKUser* user=_users.value(action->owner(),0);
        if(user){
            user->manageAction(action);
        }else{
            FKEvent* errorEvent=FKEvent::errorEvent(action);
            action->deleteLater();
            emit newEvent(errorEvent);
        }
    }
}

void FKClient::setName(QString name){
    if(_name!=QString())_clients.remove(_name);
    if(name!=QString())_clients[name]=this;
    _name=name;
}

QString FKClient::name() const{
    return _name;
}

void FKClient::A_setName(FKEvent* event){
    setName(event->value().toString());
    updateProp(FKEventName::c_name);
    applyUpdates();
}

void FKClient::A_setPassword(FKEvent* event){
    setPassword(event->value().toString());
    updateProp(FKEventName::c_password);
    applyUpdates();
}

void FKClient::setPassword(QString password){
    _password=password;
}

QString FKClient::password() const{
    return _password;
}

void FKClient::E_createObjects(FKEvent* event){
    FKObjectsData data(event->value().value<FKObjectsData>());
    QList<FKRoom*> rooms;
    FKRoom* room;
    foreach(qint32 roomID, data.roomList()){
        room=qobject_cast<FKRoom*>(getObject(roomID));
        if(roomID>=0 && !room){
            FK_MLOGV("Error parsing room id while create object. Room id", roomID)
            return;
        }
        rooms.append(room);
    }
    qint32 id, index;
    QString className;
    while(data.getNext(className, index, id)){
        FKObject* obj=qobject_cast<FKObject*>(createObject(className, id));
        if(obj){
            obj->setRoom(rooms.value(index,0));
            FK_MLOGV("Object recieved",className)
        }
    }
}

void FKClient::E_deleteObjects(FKEvent* event){
    QList<qint32> list(event->value().value<QList<qint32> >());
    FKObject* obj;
    FK_MLOG("objects parsed for deleting")
    foreach(qint32 id, list){
        obj=getObject(id);
        FK_MLOGV("deleting object id:",id)
        if(obj){
            obj->deleteFKObject();
        }else{
            FK_MLOGV("Error deleting object. Cannot find object id",id)
        }
    }
}

//FKObjectsData FKClient::emptyObjectData(){
//    return FKObjectsData();
//}

QStringList FKClient::getUserList() const{
    return _userList;
}

void FKClient::setUserList(QStringList userList){
    _userList=userList;
    updateProp(FKEventName::c_userList);
    QStringList lst(_userList);
    QStringList cur;
    foreach(FKUser* u,_users){
        lst.removeOne(u->userName());
        cur.append(u->userName());
    }
    emit userListChanged(lst);
    emit currentUserListChanged(cur);
}

void FKClient::A_addUserToList(FKEvent *event){
    QString userName(event->value().toString());
    if(userName==QString()){
        //emit userListChanged(_userList);
        setUserList(_userList);
        applyUpdates();
    }else if(_userList.contains(userName)){
        emit newEvent(FKEvent::errorEvent(QString(tr("Cannot create user that already exist")),getID()));
    }else{
        if(FKUser::addToReestr(userName)){
            _userList.append(userName);
            setUserList(_userList);
            rememberUserList();
            applyUpdates();
        }else{
            emit newEvent(FKEvent::errorEvent(QString(tr("Username already in use. Try another one")),getID()));
        }
    }
}

void FKClient::A_removeUserFromList(FKEvent *event){
    QString userName(event->value().toString());
    if(_userList.contains(userName)){
        if(FKUser::removeFromReestr(userName)){
            _userList.removeOne(userName);
            setUserList(_userList);
            rememberUserList();
            applyUpdates();
        }else{
            emit newEvent(FKEvent::errorEvent(QString(tr("Cannot find user")),getID()));
        }
    }else{
        emit newEvent(FKEvent::errorEvent(QString(tr("Cannot delete user that does not exist")),getID()));
    }
}

void FKClient::AE_gotSystemMessage(FKEvent* message){
    FK_MLOGV("Client got system message",message->value())
    emit systemMessage(message->value().toString());
}

void FKClient::rememberUserList(){
    if(_name!=QString()){
        QSettings settings;
        settings.setValue(FKPaths::users+_name,_userList);
    }
}

void FKClient::A_addUser(FKEvent* event){
    QString userName=event->value().toString();
    if(_userList.contains(userName)){
        FKUser* u=FKUser::catchUser(userName);
        u->setClient(this);
        setTotalWatching(u);
//        u->applyUpdates();
        addUser(u);
        doEvent(this,FKEventName::e_addUser,u->getID(),FKObject::ToDistantClients);
    }else{
        emit newEvent(FKEvent::errorEvent(QString(tr("Client has no such user")),getID()));
    }
}

void FKClient::A_removeUser(FKEvent* event){
    QString userName=event->value().toString();
    foreach(FKUser* u,_users){
        if(u->userName()==userName){
            removeUser(u);
            doEvent(this,FKEventName::e_removeUser,u->getID(),FKObject::ToDistantClients);
            u->deleteFKObject();
            return;
        }
    }
    emit newEvent(FKEvent::errorEvent(QString(tr("Client has no such user for deleting")),getID()));
}

void FKClient::E_addUser(FKEvent *event){
    qint32 userID=event->value().toInt();
    FKUser* user=qobject_cast<FKUser*>(getObject(userID));
    addUser(user);
}

void FKClient::E_removeUser(FKEvent *event){
    qint32 userID=event->value().toInt();
    FKUser* user=qobject_cast<FKUser*>(getObject(userID));
    removeUser(user);
}

void FKClient::addUser(FKUser* user){
    _users.insert(user->getID(),user);
//    connect(user,SIGNAL(destroyed(QObject*)),SLOT(removeUser(QObject*)));
    setUserList(_userList);
//    emit usersChanged();
}

void FKClient::removeUser(FKUser* user){
    _users.remove(user->getID());
    setUserList(_userList);
//    emit usersChanged();
}

void FKClient::removeUser(QObject* o){
    FKUser* u=static_cast<FKUser*>(o);
    removeUser(u);
}

void FKClient::releaseClient(){
    foreach (FKUser* u, _users) {
        if(u->room())u->room()->removeUser(u);
        u->deleteFKObject();
    }
    deleteFKObject();
}

QList<FKUser*> FKClient::users() const{
    return _users.values();
}

QStringList FKClient::userNames() const{
    QStringList s;
    foreach(FKUser* u,_users)s.append(u->userName());
    return s;
}

/*!
 * \brief Notify system that client was (re)connected. If need, create watched objects at client side.
 */

void FKClient::setConnected(){
    if(!_deleteTimer)return;
    _deleteTimer->stop();
    _hasObjects.insert(this);
    _connected=true;
    roomCheck();
    visionChanged();
}

/*!
 * \brief Check client's users for rooms that should be created before other objects
 */

void FKClient::roomCheck(){
    QSet<FKObject*> rooms;
    foreach (FKUser* u, _users) {
        FKRoom* r=u->room();
        if(r){
            if(checkRoomAvaliable(r->getClassName())){
                rooms.insert(r);
            }else{
                doEvent(QString(tr("Room type is not avaliable for your application")),this);
            }
        }
    }
    if(!rooms.isEmpty()){
        emit newEvent(FKEvent::createObjectsEvent(getID(),rooms));
        foreach(FKObject* obj,rooms)obj->refreshValues(this);
    }
    _hasObjects.unite(rooms);
}

bool FKClient::checkRoomAvaliable(const QString &roomClass){
    if(_avaliableRoomTypes.contains(roomClass)){
        doEvent(this,FKEventName::e_initRoomModule,QVariant(roomClass),FKObject::ToDistantClients);
        return true;
    }
    return false;
}

/*!
 * \brief Notify system that client was disconnected and stop sending events to it
 */

void FKClient::setDisconnected(){
    if(!_deleteTimer)return;
    _deleteTimer->start();
    _connected=false;
    _hasObjects.clear();
    foreach (FKUser* u, _users) {
        u->setDisconnected();
    }
}

/*!
 * \brief If needed, request appropriate events to syncronize client-side application after reconnection
 */

void FKClient::reconnect(){
    QList<FKRoom*> rooms;
    foreach (FKUser* u, _users) {
        doEvent(this,FKEventName::e_addUser,u->getID(),FKObject::ToDistantClients);
    }
    if(!_users.isEmpty())doEvent(this,FKEventName::e_submitUsers,ToDistantClients);
    foreach (FKUser* u, _users) {
        if(u->room()){
            u->room()->userConnected(u);
            doEvent(u,FKEventName::e_enterRoom,u->getRoomID(),FKObject::ToDistantClients/*,u->getID()*/);
            if(!rooms.contains(u->room()))rooms.append(u->room());
        }
    }
    if(!rooms.isEmpty()){
        if(rooms.size()!=1){
            FK_MLOG("Invalid rooms count while reconnecting")
        }
        FKRoom* r=rooms.at(0);
        r->reconnectClient(this,users()); //необходимо переписать, чтобы выполнялось только в потоке комнаты
    }
}

/*!
 * \fn bool FKClient::connected()const
 * \brief Return true if client was set connected, overwise return false
 */

//QVariant FKClient::getRoomList(){
//    return FKRoom::roomNames();
//}

void FKClient::A_updateRoomList(FKEvent* event){
    Q_UNUSED(event)
    FKRoomDataList roomList = static_cast<FKRoomDataList>(FKRoom::roomList());
//    emit refreshRoomList(roomList);
    doEvent(this,FKEventName::e_updateRoomList,QVariant::fromValue(roomList),FKObject::ToAllClients);
}

void FKClient::E_updateRoomList(FKEvent* event){
    FKRoomDataList roomDataList = event->value().value<FKRoomDataList>();
    QList<QMap<int,QVariant> > roomList(roomDataList);
    emit refreshRoomList(roomList);
}

void FKClient::pack(QDataStream &stream){
    stream<<_name<<_password<<_userList<<_serverFiles;
}

bool FKClient::load(QDataStream &stream){
    stream>>_name>>_password>>_userList>>_serverFiles;
    return true;
}

void FKClient::E_submitUsers(FKEvent *event){
    Q_UNUSED(event)
    emit submitUsers();
}

void FKClient::a_avaliableRoomTypes(FKEvent *event){
    _avaliableRoomTypes=event->value().toStringList();
}

void FKClient::e_initRoomModule(FKEvent *event){
    FKRoom::initRoomModule(event->value().toString());
    FK_MLOGV("room plugin loading",event->value())
}
